package cn.louzen.utils;

import cn.louzen.domain.SampleOrg;
import cn.louzen.domain.SampleStore;
import cn.louzen.domain.SampleUser;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hyperledger.fabric.sdk.*;
import org.hyperledger.fabric.sdk.security.CryptoSuite;

import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.Collection;
import java.util.EnumSet;
import java.util.LinkedList;
import java.util.Properties;
import java.util.concurrent.TimeUnit;

import static org.hyperledger.fabric.sdk.Channel.PeerOptions.createPeerOptions;

public class ChannelUtil {
    private static Log logger = LogFactory.getLog(ChannelUtil.class);

    // 创建新的通道
    public static Channel constructChannel(String channelName, SampleOrg sampleOrg) throws Exception {
        // 初始化HFClient，注意这是HFClient，不是HFCAClient
        HFClient client = HFClient.createNewInstance();
        client.setCryptoSuite(CryptoSuite.Factory.getCryptoSuite());
        client.setUserContext(sampleOrg.getPeerAdmin());

        // 查询是否有同名的channel，如果有就不会再创建
        if (isChannelExists(channelName)) {
            return null;
        }

        // 读取配置文件，待会从里面取数据
        Properties prop = Util.getProperties();
        // 获取文件路径前缀
        String sourcePath = prop.getProperty("sourcePath");

        // 初始化Orderer排序节点对象
        Collection<Orderer> orderers = new LinkedList<>();
        for (String ordererName : sampleOrg.getOrdererNames()) {
            // 设置orderer排序节点的配置
            Properties ordererProperties = getCryptoProperties(prop, sourcePath, ordererName);
            ordererProperties.put("grpc.NettyChannelBuilderOption.keepAliveTime",
                    new Object[]{5L, TimeUnit.MINUTES});
            ordererProperties.put("grpc.NettyChannelBuilderOption.keepAliveTimeout",
                    new Object[]{8L, TimeUnit.SECONDS});
            ordererProperties.put("grpc.NettyChannelBuilderOption.keepAliveWithoutCalls",
                    new Object[]{true});
            // 将排序节点对象加入到集合中
            logger.info(String.format(""));
            orderers.add(client.newOrderer(ordererName, sampleOrg.getOrdererLocation(ordererName), ordererProperties));

        }

        // 拿出一个orderer排序节点，稍后会向其发送创建channel的请求
        Orderer anOrderer = orderers.iterator().next();
        orderers.remove(anOrderer);

        // 找到"channelName".tx文件，channel的创建需要这个文件
        ChannelConfiguration channelConfiguration = new ChannelConfiguration(
                new File(sourcePath + prop.getProperty(channelName + ".tx")));

        // 生成名为"channelName"的channel，client向anOrderer发送创建请求
        Channel newChannel = client.newChannel(channelName, anOrderer, channelConfiguration,
                client.getChannelConfigurationSignature(channelConfiguration, sampleOrg.getPeerAdmin()));

        // 初始化Peer节点对象
        for (String peerName : sampleOrg.getPeerNames()) {
            String peerLocation = sampleOrg.getPeerLocation(peerName);

            Properties peerProperties = getCryptoProperties(prop, sourcePath, peerName);
            peerProperties.put("grpc.NettyChannelBuilderOption.maxInboundMessageSize", 9000000);

            Peer peer = client.newPeer(peerName, peerLocation, peerProperties);

            // peer加入channel时职责默认是“所有”，可以设置其不能作为事件源发送新事件到channel
            // newChannel.joinPeer(peer, createPeerOptions().setPeerRoles(PeerRole.NO_EVENT_SOURCE));
            newChannel.joinPeer(peer, createPeerOptions().setPeerRoles(EnumSet.of(Peer.PeerRole.ENDORSING_PEER,
                    Peer.PeerRole.LEDGER_QUERY, Peer.PeerRole.CHAINCODE_QUERY, Peer.PeerRole.EVENT_SOURCE)));
        }

        // 将剩余Orderer添加到channel中
        for (Orderer orderer : orderers) {
            newChannel.addOrderer(orderer);
        }

        // 给channel设置监听事件的grpc接口
        for (String eventHubName : sampleOrg.getEventHubNames()) {

            final Properties eventHubProperties = getCryptoProperties(prop, sourcePath, eventHubName);

            eventHubProperties.put("grpc.NettyChannelBuilderOption.keepAliveTime",
                    new Object[]{5L, TimeUnit.MINUTES});
            eventHubProperties.put("grpc.NettyChannelBuilderOption.keepAliveTimeout",
                    new Object[]{8L, TimeUnit.SECONDS});

            EventHub eventHub = client.newEventHub(eventHubName, sampleOrg.getEventHubLocation(eventHubName),
                    eventHubProperties);
            newChannel.addEventHub(eventHub);
        }
        // 初始化通道
        newChannel.initialize();
        // sampleStore保存通道序列化的16进制数据
        SampleStore sampleStore = SampleStore.getSampleStore();
        sampleStore.saveChannel(newChannel);

        return newChannel;
    }

    private static Properties getCryptoProperties(Properties prop, final String sourcePath, final String domainName)
            throws Exception {
        Properties cryptoProp = new Properties();

        // 读取domainName.clientCertFile的路径
        File clientCertFile = Paths.get(sourcePath, prop.getProperty(domainName + ".clientCertFile")).toFile();
        if (!clientCertFile.exists()) {
            throw new Exception(String.format("file %s is not found!", clientCertFile.getPath()));
        }
        cryptoProp.setProperty("clientCertFile", clientCertFile.getAbsolutePath());

        // 读取domainName.clientKeyFile的路径
        File clientKeyFile = Paths.get(sourcePath, prop.getProperty(domainName + ".clientKeyFile")).toFile();
        if (!clientKeyFile.exists()) {
            throw new Exception(String.format("file %s is not found!", clientKeyFile.getPath()));
        }
        cryptoProp.setProperty("clientKeyFile", clientKeyFile.getAbsolutePath());

        // 读取domainName.pemFile的路径
        File pemFile = Paths.get(sourcePath, prop.getProperty(domainName + ".pemFile")).toFile();
        if (!pemFile.exists()) {
            throw new Exception(String.format("file %s is not found!", pemFile.getPath()));
        }
        cryptoProp.setProperty("pemFile", pemFile.getAbsolutePath());
        cryptoProp.setProperty("hostnameOverride", domainName);
        cryptoProp.setProperty("sslProvider", "openSSL");
        cryptoProp.setProperty("negotiationType", "TLS");

        return cryptoProp;
    }

    public static Channel getChannel(String channelName, SampleUser peerAdmin) throws Exception {
        HFClient client = HFClient.createNewInstance();
        client.setCryptoSuite(CryptoSuite.Factory.getCryptoSuite());
        client.setUserContext(peerAdmin);
        SampleStore sampleStore = SampleStore.getSampleStore();
        return sampleStore.getChannel(client, channelName).initialize();

    }

    public static boolean isChannelExists(String channelName) throws Exception {
        SampleStore sampleStore = SampleStore.getSampleStore();
        return sampleStore.getValue("channel." + channelName) != null ? true : false;
    }
}
